package net.cyndeline.GameMap.dataStructure

import Array._
import net.cyndeline.GameMap.Grid
import net.cyndeline.GameMap.Tile
import com.escalatesoft.subcut.inject.BindingModule
import com.escalatesoft.subcut.inject.Injectable

/**
 * Represents a map grid using a 2D array for constant lookup time.
 */
class MapGrid(implicit override val bindingModule: BindingModule) extends Grid with Injectable {
	private var matrix = Array.fill[Option[Tile]](1, 1) { None }
	
	override def width: Int = matrix.length
	override def height: Int = matrix(0).length
	
	override def build(width: Int, height: Int) {
	  matrix = Array.fill[Option[Tile]](width, height) { None  }
	  for (x <- 0 until width; y <- 0 until height) {
	    this.add(newTile(x, y))
	  }
	}
	
	/** Adds a single node to the grid.
	 *  
	 * The position is based on the node x/y values. These must be within
	 * the grid boundaries. Adding the node will also update it with any
	 * new neighbors it might have on the grid, as well as enabling it to have
	 * its neighbor list updated when other nodes are added.
	 * 
	 * @throws IllegalArgumentException
	 * 
	 */
	override def add(h: Tile): Unit = {
	  if (!inRange(h))
	    throw new IllegalArgumentException("Attempted to add node " + h + " outside the map (" + width + ", " + height + ").")
	  else if (matrix(h.x)(h.y).isDefined)
	    throw new IllegalArgumentException("Attempted to add node on top of another node at (" + h.x + ", " + h.y + ")")

	  /* Add the node to the matrix. */
	  matrix(h.x)(h.y) = Option(h)
	    
	  /* Fetch all its neighbors and add them as neighbors to the
	   * new node, as well as vice versa.
	   */
	  for (neighbor <- getNeighbors(h.x, h.y)) {
	    neighbor.addNeighbor(h)
	    h.addNeighbor(neighbor)
	  }

	}
	
	/** Removes a node from the grid.
	 * 
	 * Removal is based on equality and the x/y position inside the node.
	 * The removed node will also have its neighbor list emptied of all
	 * its neighbors on this grid.
	 * 
	 * @throws IllegalArgumentException
	 */
	override def remove(h: Tile): Unit = {
	  if (!inRange(h))
	    throw new IllegalArgumentException("Attempted to remove node " + h + " outside the grid (" + width + ", " + height + ").")
	  else if (!matrix(h.x)(h.y).isDefined)
	    throw new IllegalArgumentException("Attempted to remove node " + h + " that didn't exist")

		for (neighbor <- getNeighbors(h.x, h.y)) {
		  neighbor.removeNeighbor(h)
		  h.removeNeighbor(neighbor)
		}
		matrix(h.x)(h.y) = None  
	}
	
	override def get(x: Int, y: Int): Option[Tile] = 
	  if (coordinatesInRange(x, y))
		matrix(x)(y)
	  else
	    None
	
	override def getNeighbors(x: Int, y: Int): List[Tile] = {
	  val offset = if (y % 2 == 0) -1 else 1		
	  var neighbors = List[Option[Tile]]()
	  
	    if (coordinatesInRange(x + 1, y))
	      neighbors ::= matrix(x + 1)(y)
	      
	    if (coordinatesInRange(x - 1, y))
	    	neighbors ::= matrix(x - 1)(y)
	    
	    if (coordinatesInRange(x, y + 1))
	    	neighbors ::= matrix(x)(y + 1)
	    	
	    if (coordinatesInRange(x, y - 1))
	    	neighbors ::= matrix(x)(y - 1)
		
		if (coordinatesInRange(x + offset, y + 1))
			neighbors ::= matrix(x + offset)(y + 1)
		
		if (coordinatesInRange(x + offset, y - 1))
			neighbors ::= matrix(x + offset)(y - 1)
	  
	  val result = for (Some(n) <- neighbors) yield(n)
	  result
	}
	
	override def getNeighborPositions(x: Int, y: Int): List[(Int, Int)] = {
	  val offset = if (y % 2 == 0) -1 else 1		
	  var positions = List[(Int, Int)]()
	  
	    if (coordinatesInRange(x + 1, y))
	      positions ::= (x + 1, y)
	      
	    if (coordinatesInRange(x - 1, y))
	    	positions ::= (x - 1, y)
	    
	    if (coordinatesInRange(x, y + 1))
	    	positions ::= (x, y + 1)
	    	
	    if (coordinatesInRange(x, y - 1))
	    	positions ::= (x, y - 1)
		
		if (coordinatesInRange(x + offset, y + 1))
			positions ::= (x + offset, y + 1)
		
		if (coordinatesInRange(x + offset, y - 1))
			positions ::= (x + offset, y - 1)
	  
	  positions
	}
	
	override def copy: MapGrid = {
	  val cpy = new MapGrid()
	  cpy.emptyAllTiles(width, height)
	  for (m <- 0 until matrix.length; n <- 0 until matrix(0).length) {
	    if (matrix(m)(n).isDefined) {
	      val node = matrix(m)(n).get.copy
	      cpy.add(node)
	    }
	  }
	  cpy
	}
	
	/**
	 * Creates a grid with no tiles in it. Used for copying and
	 * testing.
	 */
	def emptyAllTiles(width: Int, height: Int) {
	  matrix = Array.fill[Option[Tile]](width, height) { None }
	}
	    
	private def inRange(h: Tile): Boolean = coordinatesInRange(h.x, h.y)
	
	private def coordinatesInRange(x: Int, y: Int): Boolean = 
	  if (x >= 0 && x < width && y >= 0 && y < height)
	    true
	  else 
	    false
	    
	private def newTile(x: Int, y: Int): Tile = {
	    val tile = injectOptional[Tile] getOrElse { new GridTile() }
	    tile.x = x
	    tile.y = y
	    tile
	  }
}